/*
*  This file is part of mangusta
*
*  mangusta is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  mangusta is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with mangusta; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  US
*
*  Author: <antonino.calderone@ericsson.com>, <acaldmail@gmail.com>
*
*/

#ifndef __NU_FUNCTIONS_H__
#define __NU_FUNCTIONS_H__


/* -------------------------------------------------------------------------- */

#include "nu_variant.h"
#include "nu_exception.h"
#include "nu_expr_any.h"
#include "nu_symbol_map.h"
#include "nu_rt_prog_ctx.h"
#include "nu_namespaces.h"
#include "nu_operators.h"
#include "nu_module.h"

#include <string>
#include <functional>
#include <vector>
#include <algorithm>
#include <type_traits>
#include <mutex>


/* -------------------------------------------------------------------------- */

namespace nu
{

/* -------------------------------------------------------------------------- */

using func_t = std::function < variant_t(
                  rt_prog_ctx_t &, const std::string&, const func_args_t &) > ;


/* -------------------------------------------------------------------------- */

class functions_t : protected symbol_map_t < std::string, func_t >
{
private:
   functions_t() = default;
   functions_t(const functions_t&) = delete;
   functions_t& operator=(const functions_t&) = delete;

   static functions_t* _instance;

   std::map< std::string, module_t* > _modules;
   friend class module_t;

   using super_t = symbol_map_t < std::string, func_t > ;

protected:
   virtual void get_err_msg(const std::string& key, std::string& err) const
   {
      err = "'" + key + "()' not defined";
   }

   mutable std::recursive_mutex _mtx;

public:

   func_t & operator [] (const std::string& key)
   {
      std::lock_guard<std::recursive_mutex> _lock(_mtx);

      return super_t::operator[](key);
   }


   const func_t & operator [] (const std::string& key) const
   {
      std::lock_guard<std::recursive_mutex> _lock(_mtx);

      return super_t::operator[](key);
   }


   virtual bool is_defined(const std::string& name) const throw() override
   {
      std::lock_guard<std::recursive_mutex> _lock(_mtx);

      return super_t::is_defined(name);
   }


   virtual bool define(const std::string& name, const func_t& value) override
   {
      std::lock_guard<std::recursive_mutex> _lock(_mtx);

      return super_t::define(name, value);
   }

   virtual void erase(const std::string& name) override
   {
      std::lock_guard<std::recursive_mutex> _lock(_mtx);

      super_t::erase(name);
   }


   static functions_t& get_instance();


   static void check_arg_num(
      const nu::func_args_t & args,
      int expected_arg_num,
      const std::string & fname);


   static void get_functor_vargs(
      rt_prog_ctx_t & ctx,
      const std::string& name,
      const nu::func_args_t & args,
      const std::vector<variant_t::type_t> & check_vect,
      std::vector< variant_t > & vargs);
};


/* -------------------------------------------------------------------------- */

// RT F()(T arg)
template <class F, typename RT, typename T>
variant_t functor_RT_T(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args,
   variant_t::type_t argt)
{
   std::vector< variant_t::type_t > check_vect = { argt };
   std::vector< variant_t > vargs;
   functions_t::get_functor_vargs(ctx, name, args, check_vect, vargs);

   auto arg = static_cast<T>(vargs[0]);

   return nu::variant_t(RT(F()(arg)));
}


/* -------------------------------------------------------------------------- */

// RT F()(T1 arg1, T2 arg2)
template <class F, typename RT, typename T1, typename T2>
variant_t functor_RT_T1_T2(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args,
   variant_t::type_t argt1,
   variant_t::type_t argt2)
{
   std::vector< variant_t::type_t > check_vect = { argt1, argt2 };
   std::vector< variant_t > vargs;
   functions_t::get_functor_vargs(ctx, name, args, check_vect, vargs);

   auto arg1 = static_cast<T1>(vargs[0]);
   auto arg2 = static_cast<T2>(vargs[1]);

   return nu::variant_t(RT(F()(arg1, arg2)));
}


/* -------------------------------------------------------------------------- */

// RT F()(T1 arg1, T2 arg2, T3 arg3)
template <class F, typename RT, typename T1, typename T2, typename T3>
variant_t functor_RT_T1_T2_T3(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args,
   variant_t::type_t argt1,
   variant_t::type_t argt2,
   variant_t::type_t argt3)
{
   std::vector< variant_t::type_t > check_vect = { argt1, argt2, argt3 };
   std::vector< variant_t > vargs;
   functions_t::get_functor_vargs(ctx, name, args, check_vect, vargs);

   auto arg1 = static_cast<T1>(vargs[0]);
   auto arg2 = static_cast<T2>(vargs[1]);
   auto arg3 = static_cast<T3>(vargs[2]);

   return nu::variant_t(RT(F()(arg1, arg2, arg3)));
}


/* -------------------------------------------------------------------------- */

// RT F()(T1 arg1, T2 arg2, T3 arg3)
template <class F, typename RT, typename T1, typename T2, typename T3, typename T4>
variant_t functor_RT_T1_T2_T3_T4(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args,
   variant_t::type_t argt1,
   variant_t::type_t argt2,
   variant_t::type_t argt3,
   variant_t::type_t argt4)
{
   std::vector< variant_t::type_t > check_vect = { argt1, argt2, argt3, argt4 };
   std::vector< variant_t > vargs;
   functions_t::get_functor_vargs(ctx, name, args, check_vect, vargs);

   auto arg1 = static_cast<T1>(vargs[0]);
   auto arg2 = static_cast<T2>(vargs[1]);
   auto arg3 = static_cast<T3>(vargs[2]);
   auto arg4 = static_cast<T3>(vargs[3]);

   return nu::variant_t(RT(F()(arg1, arg2, arg3, arg4)));
}


/* -------------------------------------------------------------------------- */

// int F()(float)
template <class F>
variant_t functor_int_float(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T<F, int, float>(
             ctx,
             name,
             args,
             variant_t::type_t::FLOAT);
}


/* -------------------------------------------------------------------------- */

// int F()(double)
template <class F>
variant_t functor_int_double(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T<F, int, double>(
             ctx,
             name,
             args,
             variant_t::type_t::DOUBLE);
}


/* -------------------------------------------------------------------------- */

// int F()(string)
template <class F>
variant_t functor_int_string(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T<F, int, std::string>(
             ctx,
             name,
             args,
             variant_t::type_t::STRING);
}


/* -------------------------------------------------------------------------- */

// string F()(int)
template <class F>
variant_t functor_string_int(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T<F, std::string, int>(
             ctx,
             name,
             args,
             variant_t::type_t::INTEGER);
}


/* -------------------------------------------------------------------------- */

// string F()(float)
template <class F>
variant_t functor_string_float(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T<F, std::string, float>(
             ctx,
             name,
             args,
             variant_t::type_t::FLOAT);
}


/* -------------------------------------------------------------------------- */

// string F()(double)
template <class F>
variant_t functor_string_double(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T<F, std::string, double>(
             ctx,
             name,
             args,
             variant_t::type_t::DOUBLE);
}


/* -------------------------------------------------------------------------- */

// string F()(string)
template <class F>
variant_t functor_string_string(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T<F, std::string, std::string>(
             ctx,
             name,
             args,
             variant_t::type_t::STRING);
}


/* -------------------------------------------------------------------------- */

// float F()(string)
template <class F>
variant_t functor_float_string(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T<F, float, std::string>(
             ctx,
             name,
             args,
             variant_t::type_t::STRING);
}


/* -------------------------------------------------------------------------- */

// double F()(string)
template <class F>
variant_t functor_double_string(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T<F, double, std::string>(
             ctx,
             name,
             args,
             variant_t::type_t::STRING);
}


/* -------------------------------------------------------------------------- */

// string F()(string, int)
template <class F>
variant_t functor_string_string_int(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T1_T2<F, std::string, std::string, int>(
             ctx,
             name,
             args,
             variant_t::type_t::STRING,
             variant_t::type_t::INTEGER);
}


/* -------------------------------------------------------------------------- */

// int F()(string, string)
template <class F>
variant_t functor_int_string_string(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T1_T2<F, int, std::string, std::string>(
             ctx,
             name,
             args,
             variant_t::type_t::STRING,
             variant_t::type_t::STRING);
}


/* -------------------------------------------------------------------------- */

// int F()(string, int)
template <class F>
variant_t functor_int_string_int(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T1_T2<F, int, std::string, int>(
             ctx,
             name,
             args,
             variant_t::type_t::STRING,
             variant_t::type_t::INTEGER);
}


/* -------------------------------------------------------------------------- */

// int F()(int, int, int)
template <class F>
variant_t functor_int_int_int_int(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T1_T2_T3<F, int, int, int, int>(
             ctx,
             name,
             args,
             variant_t::type_t::INTEGER,
             variant_t::type_t::INTEGER,
             variant_t::type_t::INTEGER);
}


/* -------------------------------------------------------------------------- */

// int F()(int, int, int, int)
template <class F>
variant_t functor_int_int_int_int_int(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T1_T2_T3_T4<F, int, int, int, int, int>(
             ctx,
             name,
             args,
             variant_t::type_t::INTEGER,
             variant_t::type_t::INTEGER,
             variant_t::type_t::INTEGER,
             variant_t::type_t::INTEGER);
}

/* -------------------------------------------------------------------------- */

// string F()(string, int, int)
template <class F >
variant_t functor_string_string_int_int(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T1_T2_T3<F, std::string, std::string, int, int>(
             ctx,
             name,
             args,
             variant_t::type_t::STRING,
             variant_t::type_t::INTEGER,
             variant_t::type_t::INTEGER);
}


/* -------------------------------------------------------------------------- */

// string F()(string, int, string)
template <class F >
variant_t functor_string_string_int_string(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return functor_RT_T1_T2_T3<F, std::string, std::string, int, std::string>(
             ctx,
             name,
             args,
             variant_t::type_t::STRING,
             variant_t::type_t::INTEGER,
             variant_t::type_t::STRING);
}


/* -------------------------------------------------------------------------- */

// T F()(T) where T is int
template <class T, class F>
variant_t math_functor_aux(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args,
   std::true_type)
{
   return functor_RT_T<F, T, T>(ctx, name, args, { variant_t::type_t::INTEGER });
}


/* -------------------------------------------------------------------------- */

// T F()(T) where T is double
template <class T, class F>
variant_t math_functor_aux(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args,
   std::false_type)
{
   return functor_RT_T<F, T, T>(ctx, name, args, { variant_t::type_t::DOUBLE });
}


/* -------------------------------------------------------------------------- */

// T F()(T) where T might be either float or int
template <class T, class F>
variant_t math_functor(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   return math_functor_aux<T, F>(ctx, name, args, std::is_integral<T>());
}


/* -------------------------------------------------------------------------- */

// T F()(T,T) where T might be either float or int
template <class T, class F>
variant_t math_functor2(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   if (std::is_integral<T>::value)
   {
      return functor_RT_T1_T2<F, T, T, T>(
                ctx,
                name,
                args,
                variant_t::type_t::INTEGER,
                variant_t::type_t::INTEGER
             );
   }

   return functor_RT_T1_T2<F, T, T, T>(
             ctx,
             name,
             args,
             variant_t::type_t::DOUBLE,
             variant_t::type_t::DOUBLE);
}


/* -------------------------------------------------------------------------- */

} // namespace nu

#endif // __NU_FUNCTIONS_H__
